package org.freespace.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.freespace.model.Job;
import org.freespace.model.JobDetail;
import org.freespace.model.JobMachine;
import org.freespace.service.IJobDetailService;
import org.freespace.service.IJobMachineService;
import org.freespace.service.IJobService;
import org.freespace.utils.enums.MetricType;
import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
import org.influxdb.dto.Query;
import org.influxdb.dto.QueryResult;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Slf4j
@Component
public class InfluxDBUtils {

    Map<JSONObject, InfluxDB> dbMap = new HashMap<>();

    @Bean
    public void initInfluxDB() {
//        [{"start_job_id":"0","end_job_id":"10000000","db_url":"http://192.168.30.10","db":"performance_db"}, .....]
        //实际生产环境可以配置多个influx，实现分库


        String influxdbConf = "[{\"start_job_id\":0,\"end_job_id\":10000000,\"db_url\":\"http://192.168.30.10:8086\",\"db\":\"jmeter_db\",\"performance_url\":\"http://192.168.30.10:3000/d/cbd1c3d6-da11-4c29-98ca-56d1ea4b2875/perfomance-metric?orgId=1&var-application=\"}]";
        JSONArray jsonArray = JSONUtil.parseArray(influxdbConf);
        for (Object o : jsonArray) {
            JSONObject dbConf = (JSONObject) o;
            String dbUrl = dbConf.get("db_url").toString();
            String db = dbConf.get("db").toString();

            InfluxDB influxDB = InfluxDBFactory.connect(dbUrl);
            if (!influxDB.databaseExists(db)) {
                influxDB.createDatabase(db);
            }

            influxDB.setLogLevel(InfluxDB.LogLevel.NONE);
            influxDB.setDatabase(db);

            dbMap.put(dbConf, influxDB);
        }
    }

    public InfluxDB getInfluxDB(Integer jobId) {
        Set<JSONObject> dbConfs = dbMap.keySet();
        for (JSONObject dbConf : dbConfs) {
            Integer startJobId = (Integer) dbConf.get("start_job_id");
            Integer endJobId = (Integer) dbConf.get("end_job_id");
            if (jobId > startJobId && jobId < endJobId) {
                return dbMap.get(dbConf);
            }
        }
        return null;
    }

    public String getDB(Integer jobId) {
        Set<JSONObject> dbConfs = dbMap.keySet();
        for (JSONObject dbConf : dbConfs) {
            Integer startJobId = (Integer) dbConf.get("start_job_id");
            Integer endJobId = (Integer) dbConf.get("end_job_id");
            if (jobId > startJobId && jobId < endJobId) {
                return (String) dbConf.get("db");
            }
        }
        return null;
    }

    public String getDBUrl(Integer jobId) {
        Set<JSONObject> dbConfs = dbMap.keySet();
        for (JSONObject dbConf : dbConfs) {
            Integer startJobId = (Integer) dbConf.get("start_job_id");
            Integer endJobId = (Integer) dbConf.get("end_job_id");
            if (jobId > startJobId && jobId < endJobId) {
                return (String) dbConf.get("db_url");
            }
        }
        return null;
    }

    public String getPerformanceUrl(Integer jobId) {
        Set<JSONObject> dbConfs = dbMap.keySet();
        for (JSONObject dbConf : dbConfs) {
            Integer startJobId = (Integer) dbConf.get("start_job_id");
            Integer endJobId = (Integer) dbConf.get("end_job_id");
            if (jobId > startJobId && jobId < endJobId) {
                return (String) dbConf.get("performance_url");
            }
        }
        return null;
    }


    public Map<String, Object> queryAggregateReport(Integer jobId) {
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> results = getInfluxDbData(jobId);
        if (result.isEmpty()) {
//            throw new
        }

        result = results.get(results.size() - 1);

        return result;
    }

    public List<Map<String, Object>> getInfluxDbData(Integer jobId) {
        IJobService jobService = (IJobService) SpringBeanUtil.getBean("jobService");
        Job job = jobService.getById(jobId);

        String sql = "select sum(\"count\") as samples, mean(\"avg\") as avg, " +
                " mean(\"pct90.0\") as line90, mean(\"pct95.0\") as line95,mean(\"pct99.0\") as line99, min(\"min\") as min," +
                " max(\"max\") as max, sum(\"countError\") as error, (sum(\"countError\")/sum(\"count\")) as errorRate," +
                " sum(\"count\")/count(\"count\") as throughput, sum(\"rb\") as received, sum(\"sb\") as sent" +
                " from \"jmeter\" where application = '" + jobId + "' and \"statut\" = 'all' group by \"transaction\" fill(null)";
        QueryResult queryResult = getInfluxDB(jobId).query(new Query(sql, getDB(jobId)));
        List<QueryResult.Result> resultList = queryResult.getResults();

        double min = 0;
        double max = 0;
        Double allTime = 0d;
        Double allSample = 0d;
        List<List<Object>> rs = null;
        Map<String, Map> totals = new HashMap<>();
        List<Map<String, Object>> result = new ArrayList<>();
        if (!resultList.isEmpty()) {
            QueryResult.Result qs = resultList.get(0);
            if (qs != null) {
                List<QueryResult.Series> series = qs.getSeries();
                if (!series.isEmpty()) {
                    for (QueryResult.Series serie : series) {
                        rs = serie.getValues();
                        String label = serie.getTags().get("transaction");

                        if (ObjectUtils.isNotEmpty(rs)) {
                            int machineNum = getMachineNum(jobId);
                            for (List<Object> objs : rs) {
                                log.info("jobId:{} objs:{}", jobId, objs);
                                Map<String, Object> map = new HashMap<>();
                                map.put("label", label.equals("all") ? "Total" : label);
                                map.put("samples", new BigDecimal((Double) objs.get(1)).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue());
                                map.put("line90", new BigDecimal((Double) objs.get(3)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
                                map.put("line95", new BigDecimal((Double) objs.get(4)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
                                map.put("line99", new BigDecimal((Double) objs.get(5)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());

                                if (!label.equals("all")) {

                                    Map<String, Double> mm = getAvgMap(jobId, label);
                                    Double time = mm.get("time");
                                    allTime = allTime + time;

                                    Double sample = mm.get("sample");
                                    allSample = allSample + sample;

                                    //接口平均值
                                    map.put("avg", new BigDecimal(time / sample).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());

                                    // all那组数据，min max都取每个接口的最x值
                                    double minValue = new BigDecimal((Double) objs.get(6)).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue();
                                    double maxValue = new BigDecimal((Double) objs.get(7)).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue();
                                    map.put("min", minValue);
                                    map.put("max", maxValue);
                                    min = min < minValue ? min : minValue;
                                    max = max > maxValue ? max : maxValue;
                                }

                                if (label.equals("all")) {
                                    double error = objs.get(8) == null ? 0 : new BigDecimal((Double) objs.get(8)).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue();
                                    map.put("error", error);
                                    map.put("errorPercent", error == 0 ? 0.00 : new BigDecimal((Double) objs.get(9)).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP));
                                } else {
                                    map.put("error", getErrorCount(jobId, label));
                                    double errorPercent = getErrorCountPercent(jobId, label);
                                    map.put("errorPercent", new BigDecimal(errorPercent).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP));
                                }


                                map.put("tps", new BigDecimal((Double) objs.get(10) * machineNum).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
                                map.put("maxTps", getMaxTps(label, job));
                                map.put("received", new BigDecimal((Double) objs.get(11)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
                                map.put("sent", new BigDecimal((Double) objs.get(12)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());

                                if (label.equals("all")) {
                                    totals.put("Total", map);
                                } else {
                                    updateMap(map);
                                    result.add(map);
                                }
                            }
                        }
                    }
                }
            }
        }
        if (ObjectUtils.isNotEmpty(totals)) {
            Map<String, Object> map = totals.get("Total");
            map.put("min", min);
            map.put("max", max);
            //todo这里报错要解决，上报influx数据没有接口数据的问题。只有allz
            map.put("avg", new BigDecimal(allTime / allSample).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
            updateMap(map);
            result.add(map);
        }
        return result;
    }

    public List<Map<String, Object>> getEchartResult(Integer jobId, String type, String label, List<Map<String, Object>> result, String mode) {
        String apiName = "Total".equals(label) ? "all" : label;
        IJobService jobService = (IJobService) SpringBeanUtil.getBean("jobService");
        IJobDetailService jobDetailService = (IJobDetailService) SpringBeanUtil.getBean("jobDetailService");
       
        Job job = jobService.getById(jobId);
        LocalDateTime startTime = job.getStartTime();
        LocalDateTime endTime = job.getEndTime() == null ? LocalDateTime.now() : job.getEndTime().plusSeconds(5);

        JobDetail jobDetail = jobDetailService.getDynamicInfo(job.getTestplanId(), job.getId());
        String sql ="";
        if (MetricType.TPS.getName().equals(type) || MetricType.MAX.getName().equals(type)) {
            if (jobDetail != null &&  MetricType.MAX.equals(type)) {
                sql = "select max(\"max\") as value from \"jmeter\" ";

            } else {
                sql = "select sum(\""+type+"\") as value from \"jmeter\" ";
            }

        } else {
            if (jobDetail != null &&  MetricType.AVG.getName().equals(type)) {
                sql = "select MEAN(\"avg\") as value from \"jmeter\" ";
            } else {
                sql = "select last(\""+type+"\") as value from \"jmeter\" ";
            }
        }
        sql += "where \"application\" = '" + jobId + "' and \"statut\" = '"+mode+"' and \"transaction\"='"+apiName+"' " +
                " and time > '" + job.getStartTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "' and time <= '" + endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "' " +
                "group by time(1s) fill(null)  TZ('Asia/Shanghai')";

        QueryResult queryResult = getInfluxDB(jobId).query(new Query(sql, getDB(jobId)));
        List<QueryResult.Result> resultList = queryResult.getResults();
        List<List<Object>> rs = null;
        if (CollectionUtil.isNotEmpty(resultList)) {
            QueryResult.Result qs = resultList.get(0);
            if (qs != null) {
                List<QueryResult.Series> series = qs.getSeries();
                if (CollectionUtil.isNotEmpty(series)) {
                    rs = series.get(0).getValues();
                }
            }
            if (CollectionUtil.isNotEmpty(rs)) {
                for (List<Object> objs : rs) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("timestamp", objs.get(0));
                    map.put("value", objs.get(1));
                    result.add(map);
                }
            }
        }
        return result;
    }

    private void updateMap(Map<String, Object> map) {
        for (String key : map.keySet()) {
            map.put(key, getDouble(map.get(key)));
        }
    }

    private Object getDouble(Object o) {
        String str = o.toString();
        String[] ss = StringUtils.split(str, ".");
        if (ss != null && ss.length == 2 && ss[1].length() == 1) {
            str += "0";
        }
        return str;
    }

    private Map<String, Double> getAvgMap(Integer jobId, String label) {
        Map<String, Double> map = new HashMap<>();

        String sql = "select \"avg\", \"count\" from \"jmeter\" where \"application\" = '" + jobId + "' and \"statut\" = 'all' ";
        if (label != null) {
            sql = sql + " and \"transaction\" = '" + label + "'";
        } else {
            sql = sql + " and \"transaction\" = 'all'";
        }

        QueryResult queryResult = getInfluxDB(jobId).query(new Query(sql, getDB(jobId)));
        List<QueryResult.Result> resultList = queryResult.getResults();
        List<List<Object>> rs = null;
        Double time = 0d;
        Double sample = 0d;
        if (!resultList.isEmpty()) {
            QueryResult.Result qs = resultList.get(0);
            if (qs != null) {
                List<QueryResult.Series> series = qs.getSeries();
                if (!series.isEmpty()) {
                    rs = series.get(0).getValues();
                    if (CollectionUtil.isNotEmpty(rs)) {
                        for (List<Object> list : rs) {
                            if (CollectionUtil.isNotEmpty(list) && list.size() > 1) {
                                if (list.get(1) != null && list.get(2) != null) {
                                    time = time + ((Double) list.get(1) * (Double) list.get(2));
                                    sample = sample + (Double) list.get(2);
                                }
                            }
                        }
                    }
                }
            }
        }

        map.put("time", time);
        map.put("sample", sample);
        return map;
    }

    public BigDecimal getMaxTps(String label, Job job) {
        LocalDateTime endTime = Optional.ofNullable(job.getEndTime()).orElse(LocalDateTime.now()).plusSeconds(5);

        String sql = "select sum(\"count\") as \"tps\" from \"jmeter\" where \"application\" = '" + job.getId() + "' and \"statut\" = 'all' and \"transaction\" = '" + label + "'" +
                " and time > '" + job.getStartTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "' and time <= '" + endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "' " +
                " group by time(1s) fill(null) TZ('Asia/Shanghai')";
        QueryResult queryResult = getInfluxDB(job.getId()).query(new Query(sql, getDB(job.getId())));
        log.info("getMaxTps.sql:{}", sql);
        List<QueryResult.Result> resultList = queryResult.getResults();
        List<List<Object>> rs = null;
        BigDecimal maxTps = null;
        if (!resultList.isEmpty()) {
            QueryResult.Result qs = resultList.get(0);
            if (qs != null) {
                List<QueryResult.Series> series = qs.getSeries();
                if (CollectionUtil.isNotEmpty(series)) {
                    rs = series.get(0).getValues();
                }
            }
            log.info("jobId:{} label:{} rs:{}", job.getId(), label, rs);

            List<Double> tpsPoints = new ArrayList<>();
            if (CollectionUtil.isNotEmpty(rs)) {
                for (List<Object> objs : rs) {
                    if (objs.get(1) != null) {
                        tpsPoints.add((Double) objs.get(1));
                    }
                }
            }
            log.info("jobId:{} label:{} tpsPoints:{}", job.getId(), label, tpsPoints);

            if (CollectionUtil.isNotEmpty(tpsPoints)) {
                double maxTpsDouble = Collections.max(tpsPoints);
                maxTps = new BigDecimal(maxTpsDouble).setScale(2, BigDecimal.ROUND_HALF_UP);
            }
            log.info("jobId:{} label:{} maxTps:{}", job.getId(), label, maxTps);
        }
        return maxTps;
    }

    private double getErrorCountPercent(Integer jobId, String label) {
        double error = 0.00;
        String sql = "select sum(\"error\") / sum(\"all\") as \"Error Rate\" from " +
                "  (select  sum(\"count\") as \"error\" from \"jmeter\" where \"application\" = '" + jobId + "' and \"statut\" = 'ko' and \"transaction\" = '" + label + "')," +
                "  (select  sum(\"count\") as \"all\" from \"jmeter\" where \"application\" = '" + jobId + "' and \"statut\" = 'all' and \"transaction\" = '" + label + "')";
        QueryResult queryResult = getInfluxDB(jobId).query(new Query(sql, getDB(jobId)));
        List<QueryResult.Result> resultList = queryResult.getResults();
        List<List<Object>> rs = null;
        if (!resultList.isEmpty()) {
            QueryResult.Result qs = resultList.get(0);
            if (qs != null) {
                List<QueryResult.Series> series = qs.getSeries();
                if (!series.isEmpty()) {
                    rs = series.get(0).getValues();
                    if (CollectionUtil.isNotEmpty(rs)) {
                        List<Object> list = rs.get(0);
                        if (CollectionUtil.isNotEmpty(list) && list.size() > 1) {
                            if (list.get(1) != null) {
                                error = (double) list.get(1);
                            }
                        }
                    }
                }
            }
        }
        return error;
    }

    private double getErrorCount(Integer jobId, String label) {
        double error = 0;
        String sql = "select sum(\"count\") as \"error\" from \"jmeter\" where \"application\" = '" + jobId + "' and \"statut\" = 'ko' and \"transaction\" = '" + label + "'";
        QueryResult queryResult = getInfluxDB(jobId).query(new Query(sql, getDB(jobId)));
        List<QueryResult.Result> resultList = queryResult.getResults();
        List<List<Object>> rs = null;
        if (!resultList.isEmpty()) {
            QueryResult.Result qs = resultList.get(0);
            if (qs != null) {
                List<QueryResult.Series> series = qs.getSeries();
                if (!series.isEmpty()) {
                    rs = series.get(0).getValues();
                    if (CollectionUtil.isNotEmpty(rs)) {
                        List<Object> list = rs.get(0);
                        if (CollectionUtil.isNotEmpty(list) && list.size() > 1) {
                            if (list.get(1) != null) {
                                error = (double) list.get(1);
                            }
                        }
                    }
                }
            }
        }
        return error;
    }

    private int getMachineNum(Integer jobId) {
        IJobMachineService jobMachineService = (IJobMachineService) SpringBeanUtil.getBean("jobMachineService");
        List<JobMachine> list = jobMachineService.list(Wrappers.<JobMachine>lambdaQuery().eq(JobMachine::getJobId, jobId));
        if (!list.isEmpty()) {
            return list.size();
        }
        return 1;
    }


    public Integer queryThreadNum(Integer testplanId, Integer jobId) {
        String sql = "select max(\"meanAT\") as value from jmeter where \"application\" = '" + jobId + "'";
        Integer num = 0;
        QueryResult queryResult = getInfluxDB(jobId).query(new Query(sql, getDB(jobId)));
        List<QueryResult.Result> resultList = queryResult.getResults();
        List<List<Object>> rs = null;
        if (!resultList.isEmpty()) {
            QueryResult.Result qs = resultList.get(0);
            if (qs != null) {
                List<QueryResult.Series> series = qs.getSeries();
                if (!series.isEmpty()) {
                    rs = series.get(0).getValues();
                }
            }
            if (CollectionUtil.isNotEmpty(rs)) {
                List<Object> objects = rs.get(0);
                num = Double.valueOf(objects.get(1) + "").intValue();
            }
        }
        return num;
    }


    public List<Map<String, Object>> queryErrorPercentList(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.ERROR.name(), label, result, "all");
        return result;
    }

    public List<Map<String, Object>> queryTpsReportList(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.TPS.getName(), label, result, "all");
        return result;
    }

    public List<Map<String, Object>> queryOkTpsReportList(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.TPS.getName(), label, result, "ok");
        return result;
    }

    public List<Map<String, Object>> queryResponseTimeReportList(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.AVG.getName(), label, result, "all");
        return result;
    }
    public List<Map<String, Object>> queryOkResponseTimeReportList(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.AVG.getName(), label, result, "ok");
        return result;
    }

    public List<Map<String, Object>> queryMaxResponseTimeReportList(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.MAX.getName(), label, result, "all");
        return result;
    }

    public List<Map<String, Object>> queryOKMaxResponseTimeReportList(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.MAX.getName(), label, result, "ok");
        return result;
    }

    public List<Map<String, Object>> queryLine90Data(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.LINE90.getName(), label, result, "all");
        return result;
    }

    public List<Map<String, Object>> queryOkLine90Data(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.LINE90.getName(), label, result, "ok");
        return result;
    }

    public List<Map<String, Object>> queryLine95Data(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.LINE95.getName(), label, result, "all");
        return result;
    }

    public List<Map<String, Object>> queryOkLine95Data(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.LINE95.getName(), label, result, "ok");
        return result;
    }

    public List<Map<String, Object>> queryLine99Data(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.LINE99.getName(), label, result, "all");
        return result;
    }

    public List<Map<String, Object>> queryOkLine99Data(Integer jobId, String label) {
        List<Map<String, Object>> result = new ArrayList<>();
        getEchartResult(jobId, MetricType.LINE99.getName(), label, result, "ok");
        return result;
    }
}
